#!/usr/bin/python
# Filename: file_server.py

import threading
import logging
import json
import sys
import os
import time
import socket
import hashlib
import shutil

from mjs_public import singleton
from mjs_public import mjs_tool
from mjs_conf import mjs_conf

def transfer_thread_entry(file_server, username, clientsock, ip):
    file_server.transfer_thread(username, clientsock, ip)

class FileServer(singleton.Singleton, threading.Thread):
    mjs_conf_mgr = mjs_conf.MjsConf()

    def __init__(self, mjc_server):
        self.mjc_server = mjc_server
        super(FileServer, self).__init__()

    def __del__(self):
        self.serversocket.close()

    def startup(self):
        self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        host = self.mjs_conf_mgr.host

        try:
            self.serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.serversocket.bind((host, self.mjs_conf_mgr.file_server_port))
            self.serversocket.listen(self.mjs_conf_mgr.file_server_max)
            logging.info("file_server addr:%s:%d" % (socket.gethostbyname(host), self.mjs_conf_mgr.file_server_port))
        except Exception, e:
            logging.error("start file server failed: %s" % e)
            return False

        self.start()

        return True      

    def run(self):
        logging.info("file server thread accept_thread startup")
        while True:
            clientsock, addr = self.serversocket.accept()
            ip = str(addr[0])

            logging.info("accept client:%s conneted" % ip)
            username = self.mjc_server.find_user_by_ip(ip)
            if not username:
                logging.error("don't found username by %s" % ip)
                continue

            logging.info("file server: %s mapping user:%s" % (ip, username))
            t1 = threading.Thread(target=transfer_thread_entry, args=(self, username, clientsock, ip,))
            t1.setDaemon(True)
            t1.start()

    def transfer_thread(self, username, clientsock, ip):
        max_size = 1024
        connect_expire = 10
        ret = True
        
        logging.info("transfer_thread startup")

        try:
            logging.info("ready received mangjingc data")
            file_info = clientsock.recv(max_size)
            if not file_info:
                logging.error("client:%s disconnected from server" % ip)
                ret = False
        except socket.timeout:
            logging.info("client:%s timeout(%ds) disconnected" % (ip, connect_expire))
            ret = False
        except Exception, error:
            logging.info("client:%s connected Exception: %s" % (ip, error))
            ret = False

        if not ret:
            clientsock.close()
            logging.info("transfer_thread exist")
            return

        logging.debug("file_info >>> \n%s" % file_info)

        try:
            file_info_dict = json.loads(file_info)
        except Exception, e:
            logging.error("json.loads error : %s" % e)
            clientsock.send("refused: file header format error");
            time.sleep(3)
            clientsock.close()
            logging.info("transfer_thread exist")   
            return

        if 'action' in file_info_dict.keys():
            action = file_info_dict['action']
        else:
            logging.error("get 'action' attr from file_info_dict failed")
            clientsock.send("refused: file header attribute 'action' not exist");
            clientsock.close()
            logging.info("transfer_thread exist")   
            return

        if 'server_file' in file_info_dict.keys():
            server_file = file_info_dict['server_file']
        else:
            logging.error("get 'server_file' attr from file_info_dict failed")
            clientsock.send("refused: file header attribute 'server_file' not exist");
            clientsock.close()
            logging.info("transfer_thread exist")
            return   

        if 'client_file' in file_info_dict.keys():
            client_file = file_info_dict['client_file']
        else:
            logging.error("get 'client_file' attr from file_info_dict failed")
            clientsock.send("refused: file header attribute 'client_file' not exist");
            clientsock.close()
            logging.info("transfer_thread exist")
            return

        if 'size' in file_info_dict.keys():
            filesize = int(file_info_dict['size'])
        else:
            logging.error("get 'filesize' attr from file_info_dict failed")
            clientsock.send("refused: file header attribute 'size' not exist");
            clientsock.close()
            return False

        if 'md5sum' in file_info_dict.keys():
            md5sum = file_info_dict['md5sum']
        else:
            logging.error("get 'md5sum' attr from file_info_dict failed")
            clientsock.send("refused: file header attribute 'md5sum' not exist");
            clientsock.close()            
            return False   

        client_dir = self.get_client_dir(username, ip) 

        if action == 'download':
            if self.send_file(clientsock, server_file, filesize, md5sum, client_dir) == True:
                logging.info("download %s successed" % server_file)
            else:
                logging.error("download %s failed" % server_file)
        elif action == 'upload':
            if self.recv_file(clientsock, server_file, filesize, md5sum, client_dir) == True:
                logging.info("upload %s successed" % server_file)
            else:
                logging.error("upload %s failed" % server_file)
        else:
            logging.error("'action' == %s, wrong" % action)
            clientsock.send("refused: action failed");
            
        clientsock.close()
        logging.info("transfer_thread exist")     

    def send_file(self, clientsock, filename, filesize, md5sum, client_dir):
        file_path = client_dir + filename
        
        if not os.path.exists(file_path):
            logging.error('file: %s not exist' % file_path)
            clientsock.send("refused: file not exist");
            return False

        clientsock.send("allowed");
        time.sleep(1)

        ret = True
        block_size = 1024

        try:
            fd = open(file_path, 'r')
            while True:
                byts = fd.read(block_size)
                if not byts:
                    break

                sent = clientsock.send(byts)
                if sent != len(tyts):
                    logging.error('send file data filed')
                    ret = False                                  
        except Exception, error:
            logging.error('%s' % error)
            return False

        fd.close()

        return ret;

    def recv_file(self, clientsock, filename, filesize, md5sum, client_dir):
        temp_save_path = client_dir + filename

        save_dir_split = os.path.split(temp_save_path)
        if not os.path.exists(save_dir_split[0]):
            os.makedirs(save_dir_split[0])

        if os.path.exists(temp_save_path):
            os.remove(temp_save_path)

        try:
            sent = clientsock.send("allowed")
            if sent != len("allowed"):
                logging.error('send upload file ready message failed')
                return False
        except Exception, error:
            logging.error('%s' % error)
            return False                

        logging.info("ready received file: %s" % temp_save_path)

        total_size = 0
        max_size = 1024
        ret = True

        try:
            upload_file_fd = open(temp_save_path, "w+")
        except Exception, error:
            logging.error('%s' % error)
            return False

        while True:
            try:    
                data = clientsock.recv(max_size)
                if not data:
                    break;
                    # logging.error("client disconnected from server")
                    # ret = False
                    # break;

                if len(data) == 0:
                    logging.error("data == 0")
                    continue

                total_size += len(data)
                upload_file_fd.write(data)
                if total_size >= filesize:
                    break

                # logging.info("recved total_size = %d" % total_size);

            except socket.timeout:
                logging.info("client timeout disconnected")
                ret = False
                break;
            except Exception, error:
                logging.error("client connected Exception: %s" % error)
                ret = False  
                break;              
            pass

        upload_file_fd.close()

        if not ret:
            return False

        if total_size != filesize:
            error_desc = "upload file size nonmatch, client size = %d, real recved size = %d" % (filesize, total_size)
            logging.error(error_desc)
            try:
                sent = clientsock.send("error:%s" % error_desc)
            except Exception, error:
                logging.error('%s' % error)      
            return False

        real_md5sum = mjs_tool.file_md5sum(temp_save_path)
        if (real_md5sum != md5sum):
            error_desc = "upload file md5sum nonmatch, client md5 = %s, real recved md5sum = %s" % (md5sum, real_md5sum)
            logging.error(error_desc)
            try:
                sent = clientsock.send("error:%s" % error_desc)
            except Exception, error:
                logging.error('%s' % error)   
            return False   

        try:
            sent = clientsock.send("completely")
            if sent != len("completely"):
                logging.error('send upload file completely response error file: %s' % filename)
                return False
        except Exception, error:
            logging.error('%s' % error)    
            return False  

        logging.info('upload file %s completely' % temp_save_path)

        return True;

    def get_user_dir(self, username):
        return '/etc/mangjingw/user/' + username + '/';

    def get_client_dir(self, username, ip):
        return  self.get_user_dir(username) + ip + '/';

    def get_config_dir(self, username, ip):
        return self.get_client_dir(username, ip) + '/config/'

    def get_report_dir(self, username, ip):
        return self.get_client_dir(username, ip) + '/report/'
